/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.cratos.xxgram.platf.util;

import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.util.encoders.Hex;
import org.redkale.util.Comment;
import org.redkale.util.Utility;
/**
 *
 * @author SquRab
 */
public class Cryptos {

    public static class ECDHCrypto {

        private static final String TAG = ECDHCrypto.class.getSimpleName();

        private static final String PROVIDER = "BC";

        private static final String KEGEN_ALG = "ECDH";

        private static ECDHCrypto instance;

        static {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        }

        private KeyFactory kf;
        private KeyPairGenerator kpg;

        public static synchronized ECDHCrypto getInstance() {
            if (instance == null) {
                instance = new ECDHCrypto();
            }
            return instance;
        }

        @Comment("加密")
        public String encrypt(String data, PublicKey publicKey) {
            ECPublicKeyParameters localECPublicKeyParameters = null;
            if (publicKey instanceof BCECPublicKey) {
                BCECPublicKey localECPublicKey = (BCECPublicKey) publicKey;
                org.bouncycastle.jce.spec.ECParameterSpec localECParameterSpec = localECPublicKey.getParameters();
                ECDomainParameters localECDomainParameters = new ECDomainParameters(
                        localECParameterSpec.getCurve(), localECParameterSpec.getG(),
                        localECParameterSpec.getN());
                localECPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(),
                        localECDomainParameters);
            }
            SM2Engine localSM2Engine = new SM2Engine();
            localSM2Engine.init(true, new ParametersWithRandom(localECPublicKeyParameters, new SecureRandom()));
            byte[] arrayOfByte2;
            try {
                arrayOfByte2 = localSM2Engine.processBlock(data.getBytes(), 0, data.getBytes().length);
                return new String(java.util.Base64.getEncoder().encode(arrayOfByte2));
            } catch (InvalidCipherTextException e) {
                e.printStackTrace();
                return null;
            }

        }
        
        @Comment("加密")
        public byte[] encryptBytes(byte [] data, PublicKey publicKey) {
            ECPublicKeyParameters localECPublicKeyParameters = null;
            if (publicKey instanceof BCECPublicKey) {
                BCECPublicKey localECPublicKey = (BCECPublicKey) publicKey;
                org.bouncycastle.jce.spec.ECParameterSpec localECParameterSpec = localECPublicKey.getParameters();
                ECDomainParameters localECDomainParameters = new ECDomainParameters(
                        localECParameterSpec.getCurve(), localECParameterSpec.getG(),
                        localECParameterSpec.getN());
                localECPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(),
                        localECDomainParameters);
            }
            SM2Engine localSM2Engine = new SM2Engine();
            localSM2Engine.init(true, new ParametersWithRandom(localECPublicKeyParameters, new SecureRandom()));
            byte[] arrayOfByte2;
            try {
                arrayOfByte2 = localSM2Engine.processBlock(data, 0, data.length);
                return arrayOfByte2;
            } catch (InvalidCipherTextException e) {
                e.printStackTrace();
                return null;
            }

        }

        @Comment("解密")
        public String decrypt(String encodedata, PrivateKey privateKey) {
            byte[] encodedataByte = java.util.Base64.getDecoder().decode(encodedata.getBytes());
            SM2Engine localSM2Engine = new SM2Engine();
            BCECPrivateKey sm2PriK = (BCECPrivateKey) privateKey;
            org.bouncycastle.jce.spec.ECParameterSpec localECParameterSpec = sm2PriK.getParameters();
            ECDomainParameters localECDomainParameters = new ECDomainParameters(
                    localECParameterSpec.getCurve(), localECParameterSpec.getG(),
                    localECParameterSpec.getN());
            ECPrivateKeyParameters localECPrivateKeyParameters = new ECPrivateKeyParameters(
                    sm2PriK.getD(), localECDomainParameters);
            localSM2Engine.init(false, localECPrivateKeyParameters);
            try {
                byte[] arrayOfByte3 = localSM2Engine.processBlock(encodedataByte, 0, encodedataByte.length);
                return new String(arrayOfByte3);
            } catch (InvalidCipherTextException e) {
                e.printStackTrace();
                return null;
            }
        }

         @Comment("解密")
        public byte[] decryptBytes(byte[] data, PrivateKey privateKey) {
            SM2Engine localSM2Engine = new SM2Engine();
            BCECPrivateKey sm2PriK = (BCECPrivateKey) privateKey;
            org.bouncycastle.jce.spec.ECParameterSpec localECParameterSpec = sm2PriK.getParameters();
            ECDomainParameters localECDomainParameters = new ECDomainParameters(
                    localECParameterSpec.getCurve(), localECParameterSpec.getG(),
                    localECParameterSpec.getN());
            ECPrivateKeyParameters localECPrivateKeyParameters = new ECPrivateKeyParameters(
                    sm2PriK.getD(), localECDomainParameters);
            localSM2Engine.init(false, localECPrivateKeyParameters);
            try {
                byte[] arrayOfByte3 = localSM2Engine.processBlock(data, 0, data.length);
                return arrayOfByte3;
            } catch (InvalidCipherTextException e) {
                e.printStackTrace();
                return null;
            }
        }
        
        public PublicKey loadPublicKey(String curve, byte[] data)
            throws SQLException, IOException, GeneralSecurityException
    {
        KeyFactory factory = KeyFactory.getInstance("ECDH", "BC");
       
        ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curve);
        ECCurve eccCurve = spec.getCurve();
        EllipticCurve ellipticCurve = EC5Util.convertCurve(eccCurve, spec.getSeed());
        java.security.spec.ECPoint point = ECPointUtil.decodePoint(ellipticCurve, data);
        java.security.spec.ECParameterSpec params = EC5Util.convertSpec(ellipticCurve, spec);
        ECPublicKeySpec keySpec = new ECPublicKeySpec(point, params);
        return factory.generatePublic(keySpec);
    }

    public PrivateKey loadPrivateKey(String curve, byte[] data)
            throws SQLException, IOException, GeneralSecurityException
    {
        KeyFactory factory = KeyFactory.getInstance("ECDH", "BC");
        
        ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curve);
        ECCurve eccCurve = spec.getCurve();
        EllipticCurve ellipticCurve = EC5Util.convertCurve(eccCurve, spec.getSeed());
        java.security.spec.ECParameterSpec params = EC5Util.convertSpec(ellipticCurve, spec);
        ECPrivateKeySpec keySpec = new ECPrivateKeySpec(new BigInteger(1, data), params);
        return factory.generatePrivate(keySpec);
    }
    String curve = "secp160k1";
    public String encryptData(String token, String public_key)
    {
        try
        {
            PublicKey accessPoint = loadPublicKey(curve, java.util.Base64.getDecoder().decode(public_key));
          
            Cipher c = Cipher.getInstance("ECIES");
            c.init(Cipher.ENCRYPT_MODE, accessPoint);
            String messageString = token;
            byte[] message = messageString.getBytes();
            byte[] cipher = c.doFinal(message);
            return java.util.Base64.getEncoder().encodeToString(cipher);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    public String decryptData(String message,String private_key)
    {
        try
        {
            PrivateKey privateKey = loadPrivateKey(curve, java.util.Base64.getDecoder().decode(private_key));
            byte[] d = privateKey.getEncoded();
            System.out.println("code ===" + Utility.binToHexString(privateKey.getEncoded()));
            Cipher c = Cipher.getInstance("ECIES");
            byte[] token = ((java.util.Base64.getDecoder().decode(message.getBytes())));
            c.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] plaintext = c.doFinal(token);
            
            return new String(plaintext);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
    
        
        private ECDHCrypto() {
            try {
                kf = KeyFactory.getInstance(KEGEN_ALG, PROVIDER);
                kpg = KeyPairGenerator.getInstance(KEGEN_ALG, PROVIDER);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            } catch (NoSuchProviderException e) {
                throw new RuntimeException(e);
            }
        }

        static void listCurves() {
            System.out.println();
            System.out.println(TAG + "Supported named curves:");
            Enumeration<?> names = org.bouncycastle.asn1.sec.SECNamedCurves.getNames();
            while (names.hasMoreElements()) {
                System.out.println(TAG + "\t" + (String) names.nextElement());
            }
        }

        public synchronized KeyPair generateKeyPairParams(ECParams ecp) throws Exception {
            EllipticCurve curve = toCurve(ecp);
            ECParameterSpec esSpec = new ECParameterSpec(curve, ecp.getG(), ecp.getN(), ecp.h);
            kpg.initialize(esSpec);
            return kpg.generateKeyPair();
        }

        public synchronized KeyPair generateKeyPairNamedCurve(String curveName)
                throws Exception {
            ECGenParameterSpec ecParamSpec = new ECGenParameterSpec(curveName);
            kpg.initialize(ecParamSpec);
            return kpg.generateKeyPair();
        }

//        static String base64Encode(byte[] b) {
//            try {
//                return new String(Base64.encode(b), "ASCII");
//            } catch (UnsupportedEncodingException e) {
//                throw new RuntimeException(e);
//            }
//        }
//
//        static String hex(byte[] bytes) {
//            try {
//                return new String(Hex.encode(bytes), "ASCII");
//            } catch (UnsupportedEncodingException e) {
//                throw new RuntimeException(e);
//            }
//        }
//
//        static byte[] base64Decode(String str) {
//            return Base64.decode(str);
//        }

        static EllipticCurve toCurve(ECParams ecp) {
            ECFieldFp fp = new ECFieldFp(ecp.getP());
            return new EllipticCurve(fp, ecp.getA(), ecp.getB());
        }

//        byte[] ecdh(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception {
//            ECPublicKey ecPubKey = (ECPublicKey) otherPubKey;
//            System.out.println(TAG + "public key Wx: "
//                    + ecPubKey.getW().getAffineX().toString(16));
//            System.out.println(TAG + "public key Wy: "
//                    + ecPubKey.getW().getAffineY().toString(16));
//
//            KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", PROVIDER);
//            keyAgreement.init(myPrivKey);
//            keyAgreement.doPhase(otherPubKey, true);
//
//            return keyAgreement.generateSecret();
//        }

        public synchronized PublicKey readPublicKey(String keyStr) {
            X509EncodedKeySpec x509ks = new X509EncodedKeySpec(
                    java.util.Base64.getDecoder().decode(keyStr));
            PublicKey generatePublic = null;
            try {
                generatePublic = kf.generatePublic(x509ks);
            } catch (InvalidKeySpecException ex) {
                Logger.getLogger(Cryptos.class.getName()).log(Level.SEVERE, null, ex);
            }
            return generatePublic;
        }

        public synchronized PrivateKey readPrivateKey(String keyStr)  {
            PKCS8EncodedKeySpec p8ks = new PKCS8EncodedKeySpec(
                    java.util.Base64.getDecoder().decode(keyStr));
            PrivateKey generatePrivate = null;
            try {
                generatePrivate = kf.generatePrivate(p8ks);
            } catch (InvalidKeySpecException ex) {
                Logger.getLogger(Cryptos.class.getName()).log(Level.SEVERE, null, ex);
            }
            return generatePrivate;
        }

//        PrivateKey readPrivateKeyTest(byte [] data) throws Exception {
//            PKCS8EncodedKeySpec p8ks = new PKCS8EncodedKeySpec(data);
//            return kf.generatePrivate(p8ks);
//        }
        
        public synchronized KeyPair readKeyPair(String pubKeyStr, String privKeyStr)
                throws Exception {
            return new KeyPair(readPublicKey(pubKeyStr), readPrivateKey(privKeyStr));
        }

    }

    public static final class ECParams {

        String name;
        String p;
        String a;
        String b;
        String G;
        String n;
        int h;

        BigInteger pBi;
        ECFieldFp fp;
        EllipticCurve curve;
        BigInteger aBi;
        BigInteger bBi;
        ECPoint ecpG;
        BigInteger nBi;

        public static final String secp160k1Key = "secp160k1";
        public static final String secp224k1Key = "secp224k1";
        public static final String secp192k1Key = "secp192k1";
        ECParams(String name) {
            this.name = name;
        }

        public static final ECParams secp160k1 = new ECParams(secp160k1Key);
        public static final ECParams secp224k1 = new ECParams(secp224k1Key);
        public static final ECParams secp192k1 = new ECParams(secp192k1Key);
        
        private static final Map<String, ECParams> PARAMS = new HashMap<String, ECParams>();

        static {
            secp160k1.p = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73";
            secp160k1.a = "0000000000000000000000000000000000000000";
            secp160k1.b = "0000000000000000000000000000000000000007";
            secp160k1.G = "023B4C382CE37AA192A4019E763036F4F5DD4D7EBB";
            secp160k1.n = "0100000000000000000001B8FA16DFAB9ACA16B6B3";
            secp160k1.h = 1;
            secp160k1.init();
            PARAMS.put(secp160k1.name, secp160k1);

            secp224k1.p = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D";
            secp224k1.a = "00000000000000000000000000000000000000000000000000000000";
            secp224k1.b = "00000000000000000000000000000000000000000000000000000005";
            secp224k1.G = "03A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C";
            secp224k1.n = "010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7";
            secp224k1.h = 1;
            secp224k1.init();
            PARAMS.put(secp224k1.name, secp224k1);
            
            secp192k1.p = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73";
            secp192k1.a = "0000000000000000000000000000000010000000";
            secp192k1.b = "0000000000000000000000000000000000000007";
            secp192k1.G = "023B4C382CE37AA192A4019E763036F4F5DD4D7EBB";
            secp192k1.n = "0100000000000000000001B8FA16DFAB9ACA16B6B3";
            secp192k1.h = 1;
            secp192k1.init();
            PARAMS.put(secp192k1.name, secp192k1);
        }

        static ECParams getParams(String name) {
            return PARAMS.get(name);
        }

        private void init() {
            pBi = new BigInteger(p, 16);
            fp = new ECFieldFp(pBi);
            aBi = new BigInteger(a, 16);
            bBi = new BigInteger(b, 16);
            curve = new EllipticCurve(fp, getA(), bBi);
            ecpG = ECPointUtil.decodePoint(curve, Hex.decode(G));
            nBi = new BigInteger(n, 16);
        }

        BigInteger getP() {
            return pBi;
        }

        BigInteger getA() {
            BigInteger positiveA = pBi.add(aBi);
            boolean useA = aBi.abs().equals(aBi);

            return useA ? aBi : positiveA;
        }

        BigInteger getB() {
            return bBi;
        }

        ECFieldFp getField() {
            return fp;
        }

        ECPoint getG() {
            return ecpG;
        }

        BigInteger getN() {
            return nBi;
        }
    }
      public static void main(String[] args) throws Exception {
        ECDHCrypto crypto = ECDHCrypto.getInstance();
        String private_key = "MGoCAQAwEAYHKoZIzj0CAQYFK4EEAAkEUzBRAgEBBBUA0ptesMp2dbl2l8Nsy7iqRPwFC62gBwYFK4EEAAmhLAMqAAR55hi5JsMoDj7MaJtV3tIUYcRMNFCwWJcJZOUyPeUGhabv/yY3Tgbz";
        String message = crypto.encrypt("aaa", crypto.readPublicKey("MD4wEAYHKoZIzj0CAQYFK4EEAAkDKgAEeeYYuSbDKA4+zGibVd7SFGHETDRQsFiXCWTlMj3lBoWm7/8mN04G8w=="));
        System.out.println("dcode==" + crypto.decryptData(message, private_key));
        System.out.println("xxx==" + crypto.decrypt(crypto.encrypt("aaa", crypto.readPublicKey("MD4wEAYHKoZIzj0CAQYFK4EEAAkDKgAEeeYYuSbDKA4+zGibVd7SFGHETDRQsFiXCWTlMj3lBoWm7/8mN04G8w==")), 
        crypto.readPrivateKey("MGoCAQAwEAYHKoZIzj0CAQYFK4EEAAkEUzBRAgEBBBUA0ptesMp2dbl2l8Nsy7iqRPwFC62gBwYFK4EEAAmhLAMqAAR55hi5JsMoDj7MaJtV3tIUYcRMNFCwWJcJZOUyPeUGhabv/yY3Tgbz")));
          
        
//         KeyPair keyPair = crypto.generateKeyPairNamedCurve(Cryptos.ECParams.secp160k1Key);
//        PrivateKey privateKey = keyPair.getPrivate(); // 私钥
//        PublicKey publicKey = keyPair.getPublic(); // 公钥
//        String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
//        String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
//          System.out.println("privateKeyStr ==" + privateKeyStr +", publicKeyStr ===" + publicKeyStr);
        // 1 生成公钥私钥
//        KeyPair kpA = crypto.generateKeyPairNamedCurve(ECParams.secp160k1Key);
//        PrivateKey aPrivate = kpA.getPrivate();
//        String encodeStr = java.util.Base64.getEncoder().encodeToString(aPrivate.getEncoded());
//        PrivateKey readaPrivateKey = crypto.readPrivateKey(encodeStr);
//        PublicKey aPublic = kpA.getPublic();
//        String data = "原始数据内容...";
//        System.out.println("加密前 data===" + data);
//        String encryptVal = crypto.encrypt(data, aPublic);
//        String decryptVal = crypto.decrypt(encryptVal, readaPrivateKey);
//        System.out.println("加密后 data===" + encryptVal);
//        System.out.println("解密后 data===" + decryptVal);
        
//        java.io.FileInputStream instream = new java.io.FileInputStream("D://a.jpeg");
//        byte[] pngBytes = instream.readAllBytes();
//        byte[] encryptBytes = crypto.encryptBytes(pngBytes, aPublic);
//        java.io.FileOutputStream os = new java.io.FileOutputStream("D://encryptBytes.png");
//        os.write(encryptBytes);
//        os.flush();
//        os.close();
//        
//        java.io.FileInputStream finstream = new java.io.FileInputStream("D://encryptBytes.png");
//        byte[] decryptBytes = crypto.decryptBytes(finstream.readAllBytes(), aPrivate);
//        java.io.FileOutputStream os2 = new java.io.FileOutputStream("D://decryptBytes.png");
//        os2.write(decryptBytes);
//        os2.flush();
//        System.out.println("decryptBytes==" + decryptBytes.length);
//          testDecode(crypto);
//        testDecode(crypto);
        // 1 生成公钥私钥
//        KeyPair kpA = crypto.generateKeyPairNamedCurve(ECParams.secp160k1Key);
//        PrivateKey aPrivate = kpA.getPrivate();
//        System.out.println("aPrivate ====>>" + aPrivate);
//        String privateStr = java.util.Base64.getEncoder().encodeToString(aPrivate.getEncoded());
//        System.out.println("privateStr " + privateStr);
//        PublicKey apubKey = kpA.getPublic();
//        System.out.println("apubKey ====>>" + apubKey);
//        String pubStr = java.util.Base64.getEncoder().encodeToString(apubKey.getEncoded());
//        System.out.println("pubStr " + pubStr);
//        PrivateKey readPrivateKeyTest = crypto.readPrivateKey(encodeStr);
//         System.out.println("readPrivateKeyTest ====>>" + readPrivateKeyTest);
//        crypto.readPrivateKey(keyStr)
//        PublicKey aPublic = kpA.getPublic();
//        String data = "原始数据内容...";
//        System.out.println("加密前 data===" + data);
//        String encryptVal = crypto.encrypt(data, aPublic);
//        String decryptVal = crypto.decrypt(encryptVal, aPrivate);
//        System.out.println("加密后 data===" + encryptVal);
//        System.out.println("解密后 data===" + decryptVal);
//        crypto = ECDHCrypto.getInstance();
    }
      
      public static void testDecode(ECDHCrypto crypto) throws IOException {
        java.io.FileInputStream instream = new java.io.FileInputStream("D:\\Java-Projects\\XXGramProject\\files\\1\\2a\\3t\\no\\45\\5m\\os\\rp\\k9\\2c\\tg\\e5\\0e\\2a3tno455mosrpk92ctge50eca.jpeg");
        byte[] pngBytes = instream.readAllBytes();
        PrivateKey privateKey = crypto.readPrivateKey("MGoCAQAwEAYHKoZIzj0CAQYFK4EEAAkEUzBRAgEBBBUARn6dZSfRuZQOFuOSEtwzxBY8gI6gBwYFK4EEAAmhLAMqAASRVAENOzEuefc9ltkQO1zEKMUWBnt7PNkIxYNOceR66RFQus/O7tqh");
        java.io.FileOutputStream os = new java.io.FileOutputStream("D:\\1c1.jpeg");
        os.write(crypto.decryptBytes(pngBytes, privateKey));
        os.flush();
        os.close();
      }
}
